package com.artemis.event;

import com.artemis.utils.*;

/**
 * An implementation of an {@link EventDeliverySystem} which pools the SystemEvents.
 * 
 * A {@link Pool} for a certain event type must set before you can obtain any events of that type. 
 * 
 * @author kryo
 *
 */
public class PooledEventDeliverySystem extends BasicEventDeliverySystem {
	private Bag<Pool<SystemEvent>> pools;
	
	public PooledEventDeliverySystem() {
		super();
		pools = new Bag<Pool<SystemEvent>>();
	}
	
	@Override
	public void update() {
		for(int i = 0; i < eventQueues.getCapacity(); i++) {
			Bag<SystemEvent> queue = eventQueues.get(i);
			if(queue != null) {
				if(hasPool(i)) {
					Pool<SystemEvent> pool = getPool(i);
					for(int j = 0; j < queue.size(); j++) {
						pool.free(queue.get(j));
					}
				}
				queue.clear();
			}
		}
	}
	
	/**
	 * Obtain a {@link SystemEvent} of a certain type from the Pool.
	 * 
	 * @param type the type of the event.
	 * 
	 * @return a event of that type or null if no Pool for that type is set.
	 */
	public <E extends SystemEvent> E obtain(Class<E> type) {
		return obtain(SystemEvent.EventIndexManager.getIndexFor(type), type);
	}
	
	/**
	 * Obtain a {@link SystemEvent} of a certain type from the Pool.
	 * 
	 * @param typeId the type of the event.
	 * @param type the type of the event (only used for type-casting).
	 * 
	 * @return a event of that type or null if no Pool for that type is set.
	 */
	@SuppressWarnings("unchecked")
	public <E extends SystemEvent> E obtain(int typeId, Class<E> type) {
		Pool<SystemEvent> pool = getPool(typeId);
		if(pool != null) return (E) pool.obtain();
		return null;
	}
	
	/**
	 * Obtain a {@link SystemEvent} of a certain type from the Pool.
	 * 
	 * @param typeId the type of the event.
	 * 
	 * @return a event of that type or null if no Pool for that type is set.
	 */
	public SystemEvent obtain(int typeId) {
		Pool<SystemEvent> pool = getPool(typeId);
		if(pool != null) return pool.obtain();
		return null;
	}
	
	/**
	 * Checks whether a pool for a certain event type exists.
	 * 
	 * @param type the type of the event.
	 * 
	 * @return whether it has a pool for that type.
	 */
	public <E extends SystemEvent> boolean hasPool(Class<E> type) {
		int typeId = SystemEvent.EventIndexManager.getIndexFor(type);
		return pools.isIndexWithinBounds(typeId) && pools.get(typeId) != null;
	}
	
	/**
	 * Checks whether a pool for a certain event type exists.
	 * 
	 * @param typeId the type of the event.
	 * 
	 * @return whether it has a pool for that type.
	 */
	public boolean hasPool(int typeId) {
		return pools.isIndexWithinBounds(typeId) && pools.get(typeId) != null;
	}
	
	/**
	 * Obtain a pool for a certain event type.
	 * 
	 * @param type the type of the event.
	 * 
	 * @return a pool or null if no Pool for that type is set.
	 */
	@SuppressWarnings("unchecked")
	public <E extends SystemEvent> Pool<E> getPool(Class<E> type) {
		int typeId = SystemEvent.EventIndexManager.getIndexFor(type);
		if(!pools.isIndexWithinBounds(typeId)) return null;
		return (Pool<E>) pools.get(typeId);
	}
	
	/**
	 * Obtain a pool for a certain event type.
	 * 
	 * @param typeId the type of the event.
	 * 
	 * @return a pool or null if no Pool for that type is set.
	 */
	public Pool<SystemEvent> getPool(int typeId) {
		if(!pools.isIndexWithinBounds(typeId)) return null;
		return pools.get(typeId);
	}
	
	/**
	 * Set a pool for a certain event type.
	 * 
	 * @param pool the pool for a certain type.
	 * @param type the type of the event.
	 */
	@SuppressWarnings("unchecked")
	public <E extends SystemEvent> void setPool(Pool<E> pool, Class<E> type) {
		int typeId = SystemEvent.EventIndexManager.getIndexFor(type);
		pools.ensureCapacity(typeId);
		pools.set(typeId, (Pool<SystemEvent>) pool);
	}
	
	/**
	 * Set a pool for a certain event type.
	 * 
	 * @param pool the pool for a certain type.
	 * @param typeId the type of the event.
	 */
	@SuppressWarnings("unchecked")
	public void setPool(Pool<? extends SystemEvent> pool, int typeId) {
		pools.ensureCapacity(typeId);
		pools.set(typeId, (Pool<SystemEvent>) pool);
	}
	
	/**
	 * Removes a pool for a certain event type.
	 * 
	 * @param type the type of the event.
	 */
	public <E extends SystemEvent> void removePool(Class<E> type) {
		int typeId = SystemEvent.EventIndexManager.getIndexFor(type);
		if(!pools.isIndexWithinBounds(typeId)) return;
		pools.set(typeId, null);
	}
	
	/**
	 * Removes a pool for a certain event type.
	 * 
	 * @param typeId the type of the event.
	 */
	public void removePool(int typeId) {
		if(!pools.isIndexWithinBounds(typeId)) return;
		pools.set(typeId, null);
	}
}
